在 Day 4 及 Day 5 筆者分別介紹了單例模式以及註冊者模式。把 Day 4 的「單例特性」範例程式碼改寫成如下:
trait Singleton
{
/**
* Constructor
*/
private function __construct()
{
}
/**
* 取得實例
*
* @return self
*/
public static function getInstance()
{
$name = strtolower(get_called_class());
$name = ltrim(substr($name, strrpos($name, '\\')), '\\');
if (!Registry::has($name)) {
$instance = new self();
Registry::set($instance, $name);
}
return Registry::get($name);
}
}
instance
靜態屬性不再需要,所以移除了。getInstance
方法改寫成使用註冊表來返回單例。$name = strtolower(get_called_class());
取得類別名稱 (含命名空間的名稱)。
get_called_class()
是用來取得「延遲靜態綁定」 (late static binding) 的類別名稱,專門用在靜態方法裡取得正確的類別名稱,如果是放在非靜態的方法中,作用和 get_class()
無異。
$name = ltrim(substr($name, strrpos($name, '\\')), '\\');
去除命名空間的名稱,取得類別本身的名稱。
例:APP\CORE\MAN
過濾後取得字串 man
。
if (!Registry::has($name)) {
$instance = new self();
Registry::set($instance, $name);
}
註冊表如果不存在 Man
,則實例化 Man
之後存入註冊表。
return Registry::get($name);
從註冊表取出 Man
的實例。
如果是用這樣子的方式,則取用 Man
這個類別的單例則變成既可以由本身的 getInstance
靜態方法取得,也可以從註冊表取得。
$a = Man::getInstance();
$b = Registry::get('man');
$a
和 $b
為相同的實例。
這個範例是藉由註冊表模式本身具存放物件的唯一實例的這種性質,搭配單例模式,剛好可以混合使用。不過你會發現,getInstance
內容變複雜了。程式碼沒變精簡反而變更多,筆者舉這個範例的理由很簡單:
「設計模式常常是混合使用,是活的而不是死的。是彈性的,而非死守的信條。」
下一篇文章,筆者要為大家介紹的是最常見的「工廠模式」(factory pattern)。
本文同步更新於 TerryL 部落格 Day 6 - PHP 設計模式:註冊表 (Registry) + 單例 (Singleton),歡迎前往討論。
Terry 大大
正在研究 WooCommerce Store API,發現到它就是用註冊表模式來放 Route,但裡面有個用法我不太明白:它把類別註冊到容器裡是用 SomeClass::class,查到說這樣寫就是指到類別本身,想請教這樣是不是就不用像本文中還需要用 get_called_class() 跟去除 namespace 的處理?
附上參考資料:https://stackoverflow.com/questions/30770148/what-is-class-in-php
週末來看一下 Store API 研究一下 ^^
它的 Cotainer 是這樣註冊的:
$container->register(
RoutesController::class,
function ( $container ) {
return new RoutesController(
$container->get( SchemaController::class )
);
}
);
然後取得已註冊的物件:
$container->get( RoutesController::class )
Container 的 register()
方法是這樣寫:
public function register( $id, $value ) {
if ( empty( $this->registry[ $id ] ) ) {
if ( ! $value instanceof FactoryType ) {
$value = new SharedType( $value );
}
$this->registry[ $id ] = $value;
}
}
看起來用 ::class
寫法的好處是可以把整個 namespace 一起吃進去,之後要替換 Container 就很方便~
是的。是 registry pattern。它的寫法更靈活一點,在取用的時候才 new
物件。也可以在匿名函式中下一些判斷式決定要 new 那一個物件。另外在 runtime 的時候才 new 物件也是比較省記憶體的寫法。